package com.xiangxiao.rpan.storage.service.impl;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.model.*;
import com.xiangxiao.rpan.storage.cos.CosStorageConfig;
import com.xiangxiao.rpan.storage.entity.FileItemResult;
import com.xiangxiao.rpan.storage.enums.FileTypeEnum;
import com.xiangxiao.rpan.storage.enums.StorageTypeEnum;
import com.xiangxiao.rpan.storage.param.CosParam;
import com.xiangxiao.rpan.storage.service.AbstractBaseFileService;
import com.xiangxiao.rpan.storage.utils.StringUtil;
import com.xiangxiao.rpan.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileNotFoundException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @auther xiangxiao
 * @email 573768011@qq.com
 * @data 2023/11/9 19:46
 */
@Slf4j
@Service
public class CosFileServiceImpl extends AbstractBaseFileService<CosParam> {
  private static final Logger logger = LoggerFactory.getLogger(CosFileServiceImpl.class);

  @Override
  public void init() {

  }

  private FileItemResult fileToFileItem(File file, String folderPath) {
    SimpleDateFormat sdf = new SimpleDateFormat(DateUtil.FORMAT_DATETIME_CN);

    FileItemResult fileItemResult = new FileItemResult();
    fileItemResult.setType(file.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
    fileItemResult.setTime(sdf.format(file.lastModified()));
    fileItemResult.setSize(file.length());
    fileItemResult.setName(file.getName());
    fileItemResult.setPath(folderPath);

    if (fileItemResult.getType() == FileTypeEnum.FILE) {
      fileItemResult.setUrl(getDownloadUrl(StringUtil.concat(folderPath, "/", file.getName())));
    } else {
      fileItemResult.setSize(null);
    }
    return fileItemResult;
  }

  /**
   * 检查路径合法性：
   * - 只有以 . 开头的允许通过，其他的如 ./ ../ 的都是非法获取上层文件夹内容的路径.
   *
   * @param path 文件路径
   * @throws IllegalArgumentException 文件路径包含非法字符时会抛出此异常
   */
  private static void checkPathSecurity(String path) {
    // 路径中不能包含 .. 不然可能会获取到上层文件夹的内容
    if (StrUtil.containsAny(path, "../", "..\\")) {
      throw new IllegalArgumentException("文件路径存在安全隐患: " + path);
    }
  }

  /**
   * 检查路径合法性：
   * - 不为空，且不包含 \ / 字符
   *
   * @param names 文件路径
   * @throws IllegalArgumentException 文件名包含非法字符时会抛出此异常
   */
  private static void checkNameSecurity(String... names) {
    for (String name : names) {
      // 路径中不能包含 .. 不然可能会获取到上层文件夹的内容
      if (StrUtil.containsAny(name, "\\", "/")) {
        throw new IllegalArgumentException("文件名存在安全隐患: " + name);
      }
    }
  }

  @Override
  public List<FileItemResult> fileList(String folderPath) throws Exception {
    this.checkPathSecurity(folderPath);

    List<FileItemResult> fileItemList = new ArrayList<>();

    String fullPath = StringUtil.concat(param.getFilePath() + folderPath);

    System.out.println("全路径为:" + fullPath);
    File file = new File(fullPath);

    if (!file.exists()) {
      throw new FileNotFoundException("文件不存在");
    }

    File[] files = file.listFiles();

    if (files == null) {
      return fileItemList;
    }
    for (File f : files) {
      fileItemList.add(fileToFileItem(f, folderPath));
    }

    return fileItemList;
  }

  @Override
  public String getDownloadUrl(String pathAndName) {
    return param.getDomain() + pathAndName;
  }

  @Override
  public StorageTypeEnum getStorageTypeEnum() {
    return StorageTypeEnum.COS;
  }

  @Override
  public FileItemResult getFileItem(String pathAndName) {
    SimpleDateFormat sdf = new SimpleDateFormat(DateUtil.FORMAT_DATETIME_CN);
    String fileName = FileUtil.getName(pathAndName);
    String parentPath = StringUtil.getParentPath(pathAndName);

    ObjectMetadata objectMetadata = CosStorageConfig.getCosClient().getObjectMetadata(param.getBucketName(), pathAndName);

    FileItemResult fileItemResult = new FileItemResult();
    fileItemResult.setName(fileName);
    fileItemResult.setSize(objectMetadata.getInstanceLength());
    fileItemResult.setTime(sdf.format(objectMetadata.getLastModified()));
    fileItemResult.setType(FileTypeEnum.FILE);
    fileItemResult.setPath(parentPath);
    fileItemResult.setUrl(getDownloadUrl(pathAndName));
    return fileItemResult;
  }

  @Override
  public boolean newFolder(String path, String name) {
    return false;
  }

  @Override
  public boolean deleteFile(String path, String name) {
    checkPathSecurity(path);
    checkNameSecurity(name);

    String bucketName = CosStorageConfig.Tencent_bucketName;
    String absoluteKey = path.endsWith("/") ? path + name : path + "/" + name;

    try {
      CosStorageConfig.getCosClient().deleteObject(bucketName, absoluteKey);
      return true;
    } catch (CosClientException e) {
      e.printStackTrace();
      return false;
    }
  }

  @Override
  public boolean deleteFolder(String path, String name) {
    checkPathSecurity(path);
    checkNameSecurity(name);

    String bucketName = CosStorageConfig.Tencent_bucketName;
    String folderKey = path.endsWith("/") ? path + name : path + "/" + name;
    ObjectListing objects = CosStorageConfig.getCosClient().listObjects(bucketName, folderKey);

    DeleteObjectsRequest multiObjectDeleteRequest = new DeleteObjectsRequest(bucketName);
    try {
      List<DeleteObjectsRequest.KeyVersion> keys = new ArrayList<>();

      for (COSObjectSummary objectSummary : objects.getObjectSummaries()) {
        keys.add(new DeleteObjectsRequest.KeyVersion(objectSummary.getKey()));
      }

      multiObjectDeleteRequest.setKeys(keys);
      CosStorageConfig.getCosClient().deleteObjects(multiObjectDeleteRequest);
      return true;
    } catch (MultiObjectDeleteException mde) {
      List<MultiObjectDeleteException.DeleteError> deleteErrors = mde.getErrors();
      deleteErrors.stream().map(e -> e.getMessage()).forEach(System.out::println);
      logger.error(mde.getMessage(), mde);
      return false;
    } catch (CosServiceException e) {
      e.printStackTrace();
      return false;
    }
  }

  @Override
  public boolean renameFile(String path, String name, String newName) {
    checkPathSecurity(path);
    checkNameSecurity(name);

    // 先暂时用本地文件工具类FileUtil 来操作
    // 如果文件名没变，不做任何操作.
    if (StrUtil.equals(name, newName)) {
      return true;
    }
    String srcPath = StringUtil.concat(param.getFilePath(), path, name);
    File file = new File(srcPath);
    boolean srcExists = file.exists();
    if (!srcExists) {
      throw ExceptionUtil.wrapRuntime(new FileNotFoundException("文件夹不存在."));
    }

    FileUtil.rename(file, newName, true);
    return true;
    // 只支持连接一个存储桶,因此此处不考虑移动至另外的桶的情况(存储桶启用元数据加速后才能使用该api)
    // 元数据加速功能尚在测试中,还未对外开放
//    String bucketName = CosStorageConfig.Tencent_bucketName;
//    String sourceKey = path.endsWith("/") ? path + name : path + "/" + name;
//    String targetKey = path.endsWith("/") ? path + newName : path + "/" + newName;
//    try {
//      RenameRequest renameRequest = new RenameRequest(bucketName, sourceKey, targetKey);
//      CosStorageConfig.getCosClient().rename(renameRequest);
//      CosStorageConfig.getCosClient().deleteObject(bucketName, sourceKey);
//      return true;
//    } catch (CosServiceException e) {
//      e.printStackTrace();
//      return false;
//    }

  }

  @Override
  public boolean renameFolder(String path, String name, String newName) {
    checkPathSecurity(path);
    checkNameSecurity(name);

    return renameFile(path, name, newName);
  }

  @Override
  public String getUploadUrl(String path, String name, Long size) {
    return null;
  }
}
